import fileinput
import random
import os.path
import math
import glob
# #####################################################
# Configuration
# #####################################################
ALIGN = "none"  # center centerxz bottom top none
SHADING = "smooth"  # smooth flat
TYPE = "ascii"  # ascii binary
TRANSPARENCY = "normal"  # normal invert

TRUNCATE = False
SCALE = 1.0

FRAMESTEP = 1

BAKE_COLORS = False

# default colors for debugging (each material gets one distinct color):
# white, red, green, blue, yellow, cyan, magenta
COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]

# #####################################################
# Templates
# #####################################################
TEMPLATE_FILE_ASCII = u"""\
{
    "metadata" :
    {
        "formatVersion" : 3.1,
        "sourceFile"    : "%(fname)s",
        "generatedBy"   : "OBJConverter",
        "vertices"      : %(nvertex)d,
        "faces"         : %(nface)d,
        "normals"       : %(nnormal)d,
        "colors"        : %(ncolor)d,
        "uvs"           : %(nuv)d,
        "materials"     : %(nmaterial)d
    },
    "scale" : %(scale)f,
    "materials": [%(materials)s],
    "vertices": [%(vertices)s],
    "morphTargets": [%(morphTargets)s],
    "morphColors": [%(morphColors)s],
    "normals": [%(normals)s],
    "colors": [%(colors)s],
    "uvs": [[%(uvs)s]],
    "faces": [%(faces)s]
}
"""

TEMPLATE_FILE_BIN = u"""\
{
    "metadata" :
    {
        "formatVersion" : 3.1,
        "sourceFile"    : "%(fname)s",
        "generatedBy"   : "OBJConverter",
        "vertices"      : %(nvertex)d,
        "faces"         : %(nface)d,
        "normals"       : %(nnormal)d,
        "uvs"           : %(nuv)d,
        "materials"     : %(nmaterial)d
    },
    "materials": [%(materials)s],
    "buffers": "%(buffers)s"
}
"""

TEMPLATE_VERTEX = "%f,%f,%f"
TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"

TEMPLATE_N = "%.5g,%.5g,%.5g"
TEMPLATE_UV = "%.5g,%.5g"
TEMPLATE_COLOR = "%.3g,%.3g,%.3g"
TEMPLATE_COLOR_DEC = "%d"

TEMPLATE_MORPH_VERTICES = '\t{ "name": "%s", "vertices": [%s] }'
TEMPLATE_MORPH_COLORS = '\t{ "name": "%s", "colors": [%s] }'


# #####################################################
# Utils
# #####################################################
def file_exists(filename):
    """Return true if file exists and is accessible for reading.
    Should be safer than just testing for existence due to links and
    permissions magic on Unix filesystems.
    @rtype: boolean
    """

    try:
        f = open(filename, 'r')
        f.close()
        return True
    except IOError:
        return False


def get_name(fname):
    """Create model name based of filename ("path/fname.js" -> "fname").
    """

    return os.path.splitext(os.path.basename(fname))[0]


def bbox(vertices):
    """Compute bounding box of vertex array.
    """

    if len(vertices) > 0:
        minx = maxx = vertices[0][0]
        miny = maxy = vertices[0][1]
        minz = maxz = vertices[0][2]

        for v in vertices[1:]:
            if v[0] < minx:
                minx = v[0]
            elif v[0] > maxx:
                maxx = v[0]

            if v[1] < miny:
                miny = v[1]
            elif v[1] > maxy:
                maxy = v[1]

            if v[2] < minz:
                minz = v[2]
            elif v[2] > maxz:
                maxz = v[2]

        return {'x': [minx, maxx], 'y': [miny, maxy], 'z': [minz, maxz]}

    else:
        return {'x': [0, 0], 'y': [0, 0], 'z': [0, 0]}


def translate(vertices, t):
    """Translate array of vertices by vector t.
    """

    for i in range(len(vertices)):
        vertices[i][0] += t[0]
        vertices[i][1] += t[1]
        vertices[i][2] += t[2]


def center(vertices):
    """Center model (middle of bounding box).
    """

    bb = bbox(vertices)

    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0]) / 2.0
    cy = bb['y'][0] + (bb['y'][1] - bb['y'][0]) / 2.0
    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0]) / 2.0

    translate(vertices, [-cx, -cy, -cz])


def top(vertices):
    """Align top of the model with the floor (Y-axis) and center it around X and Z.
    """

    bb = bbox(vertices)

    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0]) / 2.0
    cy = bb['y'][1]
    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0]) / 2.0

    translate(vertices, [-cx, -cy, -cz])


def bottom(vertices):
    """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
    """

    bb = bbox(vertices)

    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0]) / 2.0
    cy = bb['y'][0]
    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0]) / 2.0

    translate(vertices, [-cx, -cy, -cz])


def centerxz(vertices):
    """Center model around X and Z.
    """

    bb = bbox(vertices)

    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0]) / 2.0
    cy = 0
    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0]) / 2.0

    translate(vertices, [-cx, -cy, -cz])


def normalize(v):
    """Normalize 3d vector"""

    l = math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
    if l:
        v[0] /= l
        v[1] /= l
        v[2] /= l


def veckey3(v):
    return round(v[0], 6), round(v[1], 6), round(v[2], 6)


# #####################################################
# MTL parser
# #####################################################
def texture_relative_path(fullpath):
    texture_file = os.path.basename(fullpath.replace("\\", "/"))
    return texture_file


def parse_mtl(fname):
    """Parse MTL file.
    """

    materials = {}

    previous_line = ""
    for line in fileinput.input(fname):
        line = previous_line + line
        if line[-2:-1] == '\\':
            previous_line = line[:-2]
            continue
        previous_line = ""

        # Only split once initially for single-parameter tags that might have additional spaces in
        # their values (i.e. "newmtl Material with spaces").
        chunks = line.split(None, 1)
        if len(chunks) > 0:

            if len(chunks) > 1:
                chunks[1] = chunks[1].strip()

            # Material start
            # newmtl identifier
            if chunks[0] == "newmtl":
                if len(chunks) > 1:
                    identifier = chunks[1]
                else:
                    identifier = ""
                if not identifier in materials:
                    materials[identifier] = {}

            # Diffuse texture
            # map_Kd texture_diffuse.jpg
            if chunks[0] == "map_Kd" and len(chunks) == 2:
                materials[identifier]["mapDiffuse"] = texture_relative_path(chunks[1])

            # Ambient texture
            # map_Ka texture_ambient.jpg
            if chunks[0] == "map_Ka" and len(chunks) == 2:
                materials[identifier]["mapAmbient"] = texture_relative_path(chunks[1])

            # Specular texture
            # map_Ks texture_specular.jpg
            if chunks[0] == "map_Ks" and len(chunks) == 2:
                materials[identifier]["mapSpecular"] = texture_relative_path(chunks[1])

            # Alpha texture
            # map_d texture_alpha.png
            if chunks[0] == "map_d" and len(chunks) == 2:
                materials[identifier]["transparent"] = True
                materials[identifier]["mapAlpha"] = texture_relative_path(chunks[1])

            # Bump texture
            # map_bump texture_bump.jpg or bump texture_bump.jpg
            if (chunks[0] == "map_bump" or chunks[0] == "bump") and len(chunks) == 2:
                materials[identifier]["mapBump"] = texture_relative_path(chunks[1])

            # Split the remaining parameters.
            if len(chunks) > 1:
                chunks = [chunks[0]] + chunks[1].split()

            # Diffuse color
            # Kd 1.000 1.000 1.000
            if chunks[0] == "Kd" and len(chunks) == 4:
                materials[identifier]["colorDiffuse"] = [float(chunks[1]), float(chunks[2]),
                                                         float(chunks[3])]

            # Ambient color
            # Ka 1.000 1.000 1.000
            if chunks[0] == "Ka" and len(chunks) == 4:
                materials[identifier]["colorAmbient"] = [float(chunks[1]), float(chunks[2]),
                                                         float(chunks[3])]

            # Specular color
            # Ks 1.000 1.000 1.000
            if chunks[0] == "Ks" and len(chunks) == 4:
                materials[identifier]["colorSpecular"] = [float(chunks[1]), float(chunks[2]),
                                                          float(chunks[3])]

            # Specular coefficient
            # Ns 154.000
            if chunks[0] == "Ns" and len(chunks) == 2:
                materials[identifier]["specularCoef"] = float(chunks[1])

            # Transparency
            # Tr 0.9 or d 0.9
            if (chunks[0] == "Tr" or chunks[0] == "d") and len(chunks) == 2:
                materials[identifier]["transparent"] = True
                if TRANSPARENCY == "invert":
                    materials[identifier]["transparency"] = 1.0 - float(chunks[1])
                else:
                    materials[identifier]["transparency"] = float(chunks[1])

            # Optical density
            # Ni 1.0
            if chunks[0] == "Ni" and len(chunks) == 2:
                materials[identifier]["opticalDensity"] = float(chunks[1])

            # Illumination
            # illum 2
            #
            # 0. Color on and Ambient off
            # 1. Color on and Ambient on
            # 2. Highlight on
            # 3. Reflection on and Ray trace on
            # 4. Transparency: Glass on, Reflection: Ray trace on
            # 5. Reflection: Fresnel on and Ray trace on
            # 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
            # 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
            # 8. Reflection on and Ray trace off
            # 9. Transparency: Glass on, Reflection: Ray trace off
            # 10. Casts shadows onto invisible surfaces
            if chunks[0] == "illum" and len(chunks) == 2:
                materials[identifier]["illumination"] = int(chunks[1])

    return materials


# #####################################################
# OBJ parser
# #####################################################
def parse_vertex(text):
    """Parse text chunk specifying single vertex.
    Possible formats:
        vertex index
        vertex index / texture index
        vertex index / texture index / normal index
        vertex index / / normal index
    """

    v = 0
    t = 0
    n = 0

    chunks = text.split("/")

    v = int(chunks[0])
    if len(chunks) > 1:
        if chunks[1]:
            t = int(chunks[1])
    if len(chunks) > 2:
        if chunks[2]:
            n = int(chunks[2])

    return {'v': v, 't': t, 'n': n}


def parse_obj(fname):
    """Parse OBJ file.
    """

    vertices = []
    normals = []
    uvs = []

    faces = []

    materials = {}
    material = ""
    mcounter = 0
    mcurrent = 0

    mtllib = ""

    # current face state
    group = 0
    object = 0
    smooth = 0

    previous_line = ""
    for line in fileinput.input(fname):
        line = previous_line + line
        if line[-2:-1] == '\\':
            previous_line = line[:-2]
            continue
        previous_line = ""

        # Only split once initially for single-parameter tags that might have additional spaces in
        # their values (i.e. "usemtl Material with spaces").
        chunks = line.split(None, 1)
        if len(chunks) > 0:

            if len(chunks) > 1:
                chunks[1] = chunks[1].strip()

            # Group
            if chunks[0] == "g" and len(chunks) == 2:
                group = chunks[1]

            # Object
            if chunks[0] == "o" and len(chunks) == 2:
                object = chunks[1]

            # Materials definition
            if chunks[0] == "mtllib" and len(chunks) == 2:
                mtllib = chunks[1]

            # Material
            if chunks[0] == "usemtl":
                if len(chunks) > 1:
                    material = chunks[1]
                else:
                    material = ""
                if not material in materials:
                    mcurrent = mcounter
                    materials[material] = mcounter
                    mcounter += 1
                else:
                    mcurrent = materials[material]

            # Split the remaining parameters.
            if len(chunks) > 1:
                chunks = [chunks[0]] + chunks[1].split()

            # Vertices as (x,y,z) coordinates
            # v 0.123 0.234 0.345
            if chunks[0] == "v" and len(chunks) == 4:
                x = float(chunks[1])
                y = float(chunks[2])
                z = float(chunks[3])
                vertices.append([x, y, z])

            # Normals in (x,y,z) form; normals might not be unit
            # vn 0.707 0.000 0.707
            if chunks[0] == "vn" and len(chunks) == 4:
                x = float(chunks[1])
                y = float(chunks[2])
                z = float(chunks[3])
                normals.append([x, y, z])

            # Texture coordinates in (u,v[,w]) coordinates, w is optional
            # vt 0.500 -1.352 [0.234]
            if chunks[0] == "vt" and len(chunks) >= 3:
                u = float(chunks[1])
                v = float(chunks[2])
                w = 0
                if len(chunks) > 3:
                    w = float(chunks[3])
                uvs.append([u, v, w])

            # Face
            if chunks[0] == "f" and len(chunks) >= 4:
                vertex_index = []
                uv_index = []
                normal_index = []

                # Precompute vert / normal / uv lists
                # for negative index lookup
                vertlen = len(vertices) + 1
                normlen = len(normals) + 1
                uvlen = len(uvs) + 1

                for v in chunks[1:]:
                    vertex = parse_vertex(v)
                    if vertex['v']:
                        if vertex['v'] < 0:
                            vertex['v'] += vertlen
                        vertex_index.append(vertex['v'])
                    if vertex['t']:
                        if vertex['t'] < 0:
                            vertex['t'] += uvlen
                        uv_index.append(vertex['t'])
                    if vertex['n']:
                        if vertex['n'] < 0:
                            vertex['n'] += normlen
                        normal_index.append(vertex['n'])
                faces.append({
                    'vertex': vertex_index,
                    'uv': uv_index,
                    'normal': normal_index,
                    'material': mcurrent,
                    'group': group,
                    'object': object,
                    'smooth': smooth,
                })

            # Smooth shading
            if chunks[0] == "s" and len(chunks) == 2:
                smooth = chunks[1]

    return faces, vertices, uvs, normals, materials, mtllib


# #####################################################
# Generator - faces
# #####################################################
def setBit(value, position, on):
    if on:
        mask = 1 << position
        return (value | mask)
    else:
        mask = ~(1 << position)
        return (value & mask)


def generate_face(f, fc):
    isTriangle = (len(f['vertex']) == 3)

    if isTriangle:
        nVertices = 3
    else:
        nVertices = 4

    hasMaterial = True  # for the moment OBJs without materials get default material

    hasFaceUvs = False  # not supported in OBJ
    hasFaceVertexUvs = (len(f['uv']) >= nVertices)

    hasFaceNormals = False  # don't export any face normals (as they are computed in engine)
    hasFaceVertexNormals = (len(f["normal"]) >= nVertices and SHADING == "smooth")

    hasFaceColors = BAKE_COLORS
    hasFaceVertexColors = False  # not supported in OBJ

    faceType = 0
    faceType = setBit(faceType, 0, not isTriangle)
    faceType = setBit(faceType, 1, hasMaterial)
    faceType = setBit(faceType, 2, hasFaceUvs)
    faceType = setBit(faceType, 3, hasFaceVertexUvs)
    faceType = setBit(faceType, 4, hasFaceNormals)
    faceType = setBit(faceType, 5, hasFaceVertexNormals)
    faceType = setBit(faceType, 6, hasFaceColors)
    faceType = setBit(faceType, 7, hasFaceVertexColors)

    faceData = []

    # order is important, must match order in JSONLoader

    # face type
    # vertex indices
    # material index
    # face uvs index
    # face vertex uvs indices
    # face normal index
    # face vertex normals indices
    # face color index
    # face vertex colors indices

    faceData.append(faceType)

    # must clamp in case on polygons bigger than quads

    for i in range(nVertices):
        index = f['vertex'][i] - 1
        faceData.append(index)

    faceData.append(f['material'])

    if hasFaceVertexUvs:
        for i in range(nVertices):
            index = f['uv'][i] - 1
            faceData.append(index)

    if hasFaceVertexNormals:
        for i in range(nVertices):
            index = f['normal'][i] - 1
            faceData.append(index)

    if hasFaceColors:
        index = fc['material']
        faceData.append(index)

    return ",".join(map(str, faceData))


# #####################################################
# Generator - chunks
# #####################################################
def hexcolor(c):
    return (int(c[0] * 255) << 16) + (int(c[1] * 255) << 8) + int(c[2] * 255)


def generate_vertex(v, option_vertices_truncate, scale):
    if not option_vertices_truncate:
        return TEMPLATE_VERTEX % (v[0], v[1], v[2])
    else:
        return TEMPLATE_VERTEX_TRUNCATE % (scale * v[0], scale * v[1], scale * v[2])


def generate_normal(n):
    return TEMPLATE_N % (n[0], n[1], n[2])


def generate_uv(uv):
    return TEMPLATE_UV % (uv[0], uv[1])


def generate_color_rgb(c):
    return TEMPLATE_COLOR % (c[0], c[1], c[2])


def generate_color_decimal(c):
    return TEMPLATE_COLOR_DEC % hexcolor(c)


# #####################################################
# Morphs
# #####################################################
def generate_morph_vertex(name, vertices):
    vertex_string = ",".join(generate_vertex(v, TRUNCATE, SCALE) for v in vertices)
    return TEMPLATE_MORPH_VERTICES % (name, vertex_string)


def generate_morph_color(name, colors):
    color_string = ",".join(generate_color_rgb(c) for c in colors)
    return TEMPLATE_MORPH_COLORS % (name, color_string)


def extract_material_colors(materials, mtlfilename, basename):
    """Extract diffuse colors from MTL materials
    """

    if not materials:
        materials = {'default': 0}

    mtl = create_materials(materials, mtlfilename, basename)

    mtlColorArraySrt = []
    for m in mtl:
        if m in materials:
            index = materials[m]
            color = mtl[m].get("colorDiffuse", [1, 0, 0])
            mtlColorArraySrt.append([index, color])

    mtlColorArraySrt.sort()
    mtlColorArray = [x[1] for x in mtlColorArraySrt]

    return mtlColorArray


def extract_face_colors(faces, material_colors):
    """Extract colors from materials and assign them to faces
    """

    faceColors = []

    for face in faces:
        material_index = face['material']
        faceColors.append(material_colors[material_index])

    return faceColors


def generate_morph_targets(morphfiles, n_vertices, infile):
    skipOriginalMorph = False
    norminfile = os.path.normpath(infile)

    morphVertexData = []

    for mfilepattern in morphfiles.split():

        matches = glob.glob(mfilepattern)
        matches.sort()

        indices = range(0, len(matches), FRAMESTEP)
        for i in indices:
            path = matches[i]

            normpath = os.path.normpath(path)

            if normpath != norminfile or not skipOriginalMorph:

                name = os.path.basename(normpath)

                morphFaces, morphVertices, morphUvs, morphNormals, morphMaterials, morphMtllib = parse_obj(
                    normpath)

                n_morph_vertices = len(morphVertices)

                if n_vertices != n_morph_vertices:

                    print(
                        "WARNING: skipping morph [%s] with different number of vertices [%d] than the original model [%d]"
                        % (name, n_morph_vertices, n_vertices))

                else:

                    if ALIGN == "center":
                        center(morphVertices)
                    elif ALIGN == "centerxz":
                        centerxz(morphVertices)
                    elif ALIGN == "bottom":
                        bottom(morphVertices)
                    elif ALIGN == "top":
                        top(morphVertices)

                    morphVertexData.append((get_name(name), morphVertices))
                    print("adding [%s] with %d vertices" % (name, n_morph_vertices))

    morphTargets = ""
    if len(morphVertexData):
        morphTargets = "\n%s\n\t" % ",\n".join(
            generate_morph_vertex(name, vertices) for name, vertices in morphVertexData)

    return morphTargets


def generate_morph_colors(colorfiles, n_vertices, n_faces):
    morphColorData = []
    colorFaces = []
    materialColors = []

    for mfilepattern in colorfiles.split():

        matches = glob.glob(mfilepattern)
        matches.sort()
        for path in matches:
            normpath = os.path.normpath(path)
            name = os.path.basename(normpath)

            morphFaces, morphVertices, morphUvs, morphNormals, morphMaterials, morphMtllib = parse_obj(
                normpath)

            n_morph_vertices = len(morphVertices)
            n_morph_faces = len(morphFaces)

            if n_vertices != n_morph_vertices:

                print(
                    "WARNING: skipping morph color map [%s] with different number of vertices [%d] than the original model [%d]"
                    % (name, n_morph_vertices, n_vertices))

            elif n_faces != n_morph_faces:

                print(
                    "WARNING: skipping morph color map [%s] with different number of faces [%d] than the original model [%d]"
                    % (name, n_morph_faces, n_faces))

            else:

                morphMaterialColors = extract_material_colors(morphMaterials, morphMtllib, normpath)
                morphFaceColors = extract_face_colors(morphFaces, morphMaterialColors)
                morphColorData.append((get_name(name), morphFaceColors))

                # take first color map for baking into face colors

                if len(colorFaces) == 0:
                    colorFaces = morphFaces
                    materialColors = morphMaterialColors

                print("adding [%s] with %d face colors" % (name, len(morphFaceColors)))

    morphColors = ""
    if len(morphColorData):
        morphColors = "\n%s\n\t" % ",\n".join(
            generate_morph_color(name, colors) for name, colors in morphColorData)

    return morphColors, colorFaces, materialColors


# #####################################################
# Materials
# #####################################################
def generate_color(i):
    """Generate hex color corresponding to integer.
    Colors should have well defined ordering.
    First N colors are hardcoded, then colors are random
    (must seed random number  generator with deterministic value
    before getting colors).
    """

    if i < len(COLORS):
        #return "0x%06x" % COLORS[i]
        return COLORS[i]
    else:
        #return "0x%06x" % int(0xffffff * random.random())
        return int(0xffffff * random.random())


def value2string(v):
    if type(v) == str and v[0:2] != "0x":
        return '"%s"' % v
    elif type(v) == bool:
        return str(v).lower()
    return str(v)


def generate_materials(mtl, materials):
    """Generate JS array of materials objects
    JS material objects are basically prettified one-to-one
    mappings of MTL properties in JSON format.
    """

    mtl_array = []
    for m in mtl:
        if m in materials:
            index = materials[m]

            # add debug information
            #  materials should be sorted according to how
            #  they appeared in OBJ file (for the first time)
            #  this index is identifier used in face definitions
            mtl[m]['DbgName'] = m
            mtl[m]['DbgIndex'] = index
            mtl[m]['DbgColor'] = generate_color(index)

            if BAKE_COLORS:
                mtl[m]['vertexColors'] = "face"

            mtl_raw = ",\n".join(
                ['\t"%s" : %s' % (n, value2string(v)) for n, v in sorted(mtl[m].items())])
            mtl_string = "\t{\n%s\n\t}" % mtl_raw
            mtl_array.append([index, mtl_string])

    return ",\n\n".join([m for i, m in sorted(mtl_array)])


def generate_mtl(materials):
    """Generate dummy materials (if there is no MTL file).
    """

    mtl = {}
    for m in materials:
        index = materials[m]
        mtl[m] = {'DbgName': m, 'DbgIndex': index, 'DbgColor': generate_color(index)}
    return mtl


def generate_materials_string(materials, mtlfilename, basename):
    """Generate final materials string.
    """

    if not materials:
        materials = {'default': 0}

    mtl = create_materials(materials, mtlfilename, basename)
    return generate_materials(mtl, materials)


def create_materials(materials, mtlfilename, basename):
    """Parse MTL file and create mapping between its materials and OBJ materials.
       Eventual edge cases are handled here (missing materials, missing MTL file).
    """

    random.seed(42)  # to get well defined color order for debug colors

    # default materials with debug colors for when
    # there is no specified MTL / MTL loading failed,
    # or if there were no materials / null materials

    mtl = generate_mtl(materials)

    if mtlfilename:

        # create full pathname for MTL (included from OBJ)

        path = os.path.dirname(basename)
        fname = os.path.join(path, mtlfilename)

        if file_exists(fname):

            # override default materials with real ones from MTL
            # (where they exist, otherwise keep defaults)

            mtl.update(parse_mtl(fname))

        else:

            print("Couldn't find [%s]" % fname)

    return mtl


# #####################################################
# Faces
# #####################################################
def is_triangle_flat(f):
    return len(f['vertex']) == 3 and not (f["normal"] and SHADING == "smooth") and not f['uv']


def is_triangle_flat_uv(f):
    return len(f['vertex']) == 3 and not (f["normal"] and SHADING == "smooth") and len(f['uv']) == 3


def is_triangle_smooth(f):
    return len(f['vertex']) == 3 and f["normal"] and SHADING == "smooth" and not f['uv']


def is_triangle_smooth_uv(f):
    return len(f['vertex']) == 3 and f["normal"] and SHADING == "smooth" and len(f['uv']) == 3


def is_quad_flat(f):
    return len(f['vertex']) == 4 and not (f["normal"] and SHADING == "smooth") and not f['uv']


def is_quad_flat_uv(f):
    return len(f['vertex']) == 4 and not (f["normal"] and SHADING == "smooth") and len(f['uv']) == 4


def is_quad_smooth(f):
    return len(f['vertex']) == 4 and f["normal"] and SHADING == "smooth" and not f['uv']


def is_quad_smooth_uv(f):
    return len(f['vertex']) == 4 and f["normal"] and SHADING == "smooth" and len(f['uv']) == 4


def sort_faces(faces):
    data = {
        'triangles_flat': [],
        'triangles_flat_uv': [],
        'triangles_smooth': [],
        'triangles_smooth_uv': [],
        'quads_flat': [],
        'quads_flat_uv': [],
        'quads_smooth': [],
        'quads_smooth_uv': []
    }

    for f in faces:
        if is_triangle_flat(f):
            data['triangles_flat'].append(f)
        elif is_triangle_flat_uv(f):
            data['triangles_flat_uv'].append(f)
        elif is_triangle_smooth(f):
            data['triangles_smooth'].append(f)
        elif is_triangle_smooth_uv(f):
            data['triangles_smooth_uv'].append(f)

        elif is_quad_flat(f):
            data['quads_flat'].append(f)
        elif is_quad_flat_uv(f):
            data['quads_flat_uv'].append(f)
        elif is_quad_smooth(f):
            data['quads_smooth'].append(f)
        elif is_quad_smooth_uv(f):
            data['quads_smooth_uv'].append(f)

    return data
